#include "types.h"
#include "defs.h"
#include "param.h"
#include "mmu.h"
#include "proc.h"
#include "spinlock.h"
#define PRINT_DEBUG 1

struct msg // 消息结构体
{
    struct msg *next; // 指向下一个消息
    long type;        // 消息类型
    char *dataaddr;   // 数据地址，实际上等于msg+16
    int datasize;     // 消息长度
};

struct mq // 消息队列
{
    int key;          // 对应的key
    int status;       // 0代表未使用，1代表已使用
    struct msg *msgs; // 指向msg链表
    int maxbytes;     // 一个消息队列最大为4K
    int curbytes;     // 当前使用的字节数
    int refcount;     // 引用数
};

struct spinlock mqlock;     // 消息队列 锁
struct mq mqs[MQMAX];       // 默认系统最多八个消息队列
struct proc *wqueue[NPROC]; // 写阻塞队列
int wstart = 0;             // 写阻塞队列的指示下标

struct proc *rqueue[NPROC]; // 读阻塞队列
int rstart = 0;             // 读阻塞队列的指示下标

void mqinit()
{
#ifdef PRINT_DEBUG
    cprintf("mqinit.\n");
#endif
    initlock(&mqlock, "mqlock");
    for (int i = 0; i < MQMAX; i++)
    {
        mqs[i].status = 0;
    }
}

void addmqcount(uint mask)
{
#ifdef PRINT_DEBUG
    cprintf("addcount:%x.\n", mask);
#endif
    acquire(&mqlock);
    for (int key = 0; key < MQMAX; key++)
    {
        if (mask >> key & 1)
        {
            mqs[key].refcount++;
        }
    }
    release(&mqlock);
}

// 整合消息队列的空闲碎片
static int reloc(int mqid)
{
    struct msg *pages = mqs[mqid].msgs; // 移动消息目标地址
    struct msg *m = pages;              // 待移动的消息
    struct msg *t;
    struct msg *pre = pages;
    while (m != 0)
    {
        t = m->next;
        memmove(pages, m, m->datasize + 16);
        pages->next = (struct msg *)((char *)pages + pages->datasize + 16); // 修改下一个消息指针
        pages->dataaddr = ((char *)pages + 16);                             // 修改数据指针
        pre = pages;                                                        // 记录当前消息指针
        pages = pages->next;                                                // 下个消息的目的地址
        m = t;                                                              // 下一个待移动的消息
    }
    pre->next = 0; // 最后一个消息的next指针置0
    return 0;
}

// 遍历mqs数组，找到匹配的key值
static int findkey(int key)
{
    int idx = -1;
    for (int i = 0; i < MQMAX; i++)
    {
        if (mqs[i].status != 0 && mqs[i].key == key)
        {
            idx = i;
            break;
        }
    }
    return idx;
}

// 没有找到 key 匹配的消息队列时，需要调用newmq创建该消息队列
static int newmq(int key)
{
#ifdef PRINT_DEBUG
    cprintf("newmq\n");
#endif
    int idx = -1;
    for (int i = 0; i < MQMAX; i++) // 查看mqs数组中是否有空闲项
    {
        if (mqs[i].status == 0)
        {
            idx = i;
            break;
        }
    }
    if (idx == -1) // 消息队列用满
    {
        cprintf("newmq failed: can not get idx.\n");
        return -1;
    }
    mqs[idx].msgs = (struct msg *)kalloc(); // 为消息队列分配空间（1个页）
    if (mqs[idx].msgs == 0)                 // 消息队列的存储空间不能为NULL
    {
        cprintf("newmq failed: can not alloc page.\n");
        return -1;
    }
    mqs[idx].key = key;               // 为该消息队列设置 key 值
    mqs[idx].status = 1;              // 标记为已启用
    memset(mqs[idx].msgs, 0, PGSIZE); // 清空消息池
    mqs[idx].msgs->next = 0;          // 接下来都是初始化消息队列
    mqs[idx].msgs->datasize = 0;
    mqs[idx].maxbytes = PGSIZE;
    mqs[idx].curbytes = 16;
    proc->mqmask |= 1 << idx; // 修改当前进程的mqmask，表示使用中
    return idx;
}

int mqget(uint key)
{
    acquire(&mqlock);
    int idx = findkey(key);

    if (idx != -1) // 如果key对应的消息队列已经创建
    {
        if (!(proc->mqmask >> idx & 1)) // 如果当前进程还未使用该消息队列
        {
            proc->mqmask |= 1 << idx; // 标记本进程，使用该消息
            mqs[idx].refcount++;      // 消息队列引用计数+1
        }
        release(&mqlock);
        return idx;
    }
    idx = newmq(key); // 对应key的消息队列未创建则newmq创建
    release(&mqlock);
    return idx; // 返回该消息队列在 mqs[] 中的下标
}

int msgsnd(uint mqid, void *msg, int sz)
{
    if (mqid < 0 || MQMAX <= mqid || mqs[mqid].status == 0)
    {
        return -1;
    }
    char *data = (char *)(*((int *)(msg + 4))); // 传入的消息数据
    int *type = (int *)msg;                     // 传入的消息类型
    if (mqs[mqid].msgs == 0)
    {
        cprintf("msgsnd failed: msgs == 0.\n");
        return -1;
    }
    acquire(&mqlock);
    while (1)
    {
        if (mqs[mqid].curbytes + sz + 16 <= mqs[mqid].maxbytes) // 如果剩余空间富裕
        {
            struct msg *m = mqs[mqid].msgs;
            while (m->next != 0)
            {
                m = m->next;
            }
            m->next = (void *)m + m->datasize + 16; // 用于存储消息的起始位置
            m = m->next;                            // m 为本消息存储空间的起点
            m->type = *(type);
            m->next = 0;                     // 本消息暂无后续消息
            m->dataaddr = (void *)m + 16;    // 数据区域
            m->datasize = sz;                // 数据长度
            memmove(m->dataaddr, data, sz);  // 复制消息数据
            mqs[mqid].curbytes += (sz + 16); // 可用空间缩减
            for (int i = 0; i < rstart; i++) // 唤醒所有的阻塞进程
            {
                wakeup(rqueue[i]);
            }
            rstart = 0; // 读阻塞队列置空
            release(&mqlock);
            return 0;
        }
        else
        {
            // 空间不足，进程睡眠在wqueue阻塞队列
            cprintf("msgsnd: can not alloc: pthread:%d sleep.\n", proc->pid);
            wqueue[wstart++] = proc; // 环形队列
            sleep(proc, &mqlock);
        }
    }
    return -1;
}

int msgrcv(uint mqid, void *msg, int sz)
{
    if (mqid < 0 || MQMAX <= mqid || mqs[mqid].status == 0)
    {
        return -1;
    }
    int *type = msg;     // 待读取的消息类型
    int *data = msg + 4; // 待读取的消息目标位置
    acquire(&mqlock);

    while (1)
    {
        struct msg *m = mqs[mqid].msgs->next;
        struct msg *pre = mqs[mqid].msgs;
        while (m != 0)
        {
            if (m->type == *type) // 找到要读取的消息的类型
            {
                memmove((char *)*data, m->dataaddr, sz);
                pre->next = m->next;                      // 将已读取得消息从消息队列中删除
                mqs[mqid].curbytes -= (m->datasize + 16); // 释放消息空间
                reloc(mqid);                              // 重新整理内存
                for (int i = 0; i < wstart; i++)          // 唤醒写阻塞进程
                {
                    wakeup(wqueue[i]);
                }
                wstart = 0; // 写阻塞队列置空
                release(&mqlock);
                return 0;
            }
            pre = m;
            m = m->next;
        }
        cprintf("msgrcv: can not read: pthread:%d sleep.\n", proc->pid);
        rqueue[rstart++] = proc;
        sleep(proc, &mqlock);
    }
    return -1;
}

static void rmmq(int mqid)
{
#ifdef PRINT_DEBUG
    cprintf("rmmq: %d.\n", mqid);
#endif
    kfree((char *)mqs[mqid].msgs); // 回收物理内存
    mqs[mqid].status = 0;
}

void releasemq(uint key)
{
#ifdef PRINT_DEBUG
    cprintf("releasemq: %d.\n", key);
#endif
    int idx = findkey(key);
    if (idx != -1)
    {
        acquire(&mqlock);
        mqs[idx].refcount--;        // 引用数目减一
        if (mqs[idx].refcount == 0) // 当引用数目为0时需要回收物理内存
        {
            rmmq(idx);
        }
        release(&mqlock);
    }
}

void releasemq2(int mask)
{
#ifdef PRINT_DEBUG
    cprintf("releasemq: %x.\n", mask);
#endif
    acquire(&mqlock);
    for (int id = 0; id < MQMAX; id++)
    {
        if (mask >> id & 0x1)
        {
            mqs[id].refcount--;        // 引用数目减一
            if (mqs[id].refcount == 0) // 当引用数目为0时需要回收物理内存
            {
                rmmq(id);
            }
        }
    }
    release(&mqlock);
}